package com.yfbao.horizon.schedule.tk.meter;

import io.lettuce.core.api.StatefulRedisConnection;
import org.apache.commons.pool2.impl.GenericObjectPool;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Properties;

/**
 * Author song.xl
 * Date 2023/6/15
 * Description
 */
@Slf4j
@Component
public class JedisMetrics implements MeterBinder {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisProperties redisProperties;


    @Override
    public void bindTo(MeterRegistry meterRegistry) {
        if (redisConnectionFactory == null) {
            log.warn("Autowired RedisConnectionFactory failed");
            return;
        }
//        RedisConnection conn = RedisConnectionUtils.getConnection(redisTemplate.getConnectionFactory());
//        if (conn ==null){
//            log.warn("Autowired redisTemplate failed,can't find RedisConnection");
//        }else {
//            if (conn.getClientList() ==null){
//                log.warn("Autowired RedisConnection list is null");
//            }
//            Gauge.builder("redis.pool.count", conn, c -> c.getClientList().size())
//                    .tag("state", "current")
//                    .description("redis pool")
//                    .register(meterRegistry);
//        }

        Gauge.builder("redis.pool.count", this, instance -> {
            try (RedisConnection conn = redisTemplate.getConnectionFactory().getConnection()) {
                Properties info = conn.info("clients");
                return Double.parseDouble(info.getProperty("connected_clients", "0"));
            } catch (Exception e) {
                log.warn("Cannot fetch Redis connection count", e);
                return Double.NaN;
            }
        }).tag("state", "current").description("Number of clients connected to Redis").register(meterRegistry);

        try {
            if (redisConnectionFactory instanceof JedisConnectionFactory) {
                JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) redisConnectionFactory;

                String jedisRunType = getJedisRunType(jedisConnectionFactory);

                if (jedisRunType.equals("Cluster")) {
                    Field jedisClusterField = JedisConnectionFactory.class.getDeclaredField("cluster");
                    jedisClusterField.setAccessible(true);
                    JedisCluster jedisCluster = (JedisCluster) jedisClusterField.get(jedisConnectionFactory);
                    Map<String, JedisPool> clusterNodes = getJedisClusterNodes(jedisCluster);
                    if (clusterNodes != null) {
                        for (Map.Entry<String, JedisPool> entry : clusterNodes.entrySet()) {
                            JedisPool pool = entry.getValue();
                            String name = entry.getKey();
                            meterRegistry.gauge("redis.jedis.cluster.pool.active", Tags.of("client", name), pool, JedisPool::getNumActive);
                            meterRegistry.gauge("redis.jedis.cluster.pool.idle", Tags.of("client", name), pool, JedisPool::getNumIdle);
                            meterRegistry.gauge("redis.jedis.cluster.pool.waiting", Tags.of("client", name), pool, JedisPool::getNumWaiters);
                            log.debug("redis.jedis.cluster:{}, active:{},idle:{},waiting:{}", name, pool.getNumActive(), pool.getNumIdle(), pool.getNumWaiters());
                        }
                    } else {
                        log.debug("redis.jedis.cluster: get error");
                    }

                } else if (jedisRunType.equals("Sentinel")) {
                    Field sentinelPoolField = JedisConnectionFactory.class.getDeclaredField("pool");
                    sentinelPoolField.setAccessible(true);
                    JedisSentinelPool sentinelPool = (JedisSentinelPool) sentinelPoolField.get(jedisConnectionFactory);
                    meterRegistry.gauge("redis.jedis.sentinel.pool.active", sentinelPool, JedisSentinelPool::getNumActive);
                    meterRegistry.gauge("redis.jedis.sentinel.pool.idle", sentinelPool, JedisSentinelPool::getNumIdle);
                    meterRegistry.gauge("redis.jedis.sentinel.pool.waiting", sentinelPool, JedisSentinelPool::getNumWaiters);
                    log.debug("redis.jedis.sentinelPool, active:{},idle:{},waiting:{}", sentinelPool.getNumActive(), sentinelPool.getNumIdle(), sentinelPool.getNumWaiters());
                } else {
                    Field jedisPoolField = JedisConnectionFactory.class.getDeclaredField("pool");
                    jedisPoolField.setAccessible(true);
                    JedisPool jedisPool = (JedisPool) jedisPoolField.get(jedisConnectionFactory);
                    meterRegistry.gauge("redis.jedis.pool.active", jedisPool, JedisPool::getNumActive);
                    meterRegistry.gauge("redis.jedis.pool.idle", jedisPool, JedisPool::getNumIdle);
                    meterRegistry.gauge("redis.jedis.pool.waiting", jedisPool, JedisPool::getNumWaiters);
                    log.debug("redis.jedis.pool, active:{},idle:{},waiting:{}", jedisPool.getNumActive(), jedisPool.getNumIdle(), jedisPool.getNumWaiters());
                }
            }
            if (redisConnectionFactory instanceof LettuceConnectionFactory) {
                LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) redisConnectionFactory;
                System.out.println("启用 Lettuce");
                RedisProperties.Pool pool = redisProperties.getLettuce().getPool();
                System.out.println("max-active: " + pool.getMaxActive());
                System.out.println("max-idle: " + pool.getMaxIdle());
                System.out.println("min-idle: " + pool.getMinIdle());
                System.out.println("max-wait: " + pool.getMaxWait());
            }
        } catch (Throwable a) {

        }
    }

    private String getJedisRunType(JedisConnectionFactory jedisConnectionFactory) {
        try {
            RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection();
            if (clusterConnection != null) {
                return "Cluster";
            }
        } catch (Exception e) {
        }

        try {
            RedisSentinelConnection sentinelConnection = jedisConnectionFactory.getSentinelConnection();
            if (sentinelConnection != null) {
                return "Sentinel";
            }
        } catch (Exception e) {
        }

        return "Standalone";
    }

    // 通过反射获取所有的 JedisPool
    public Map<String, JedisPool> getJedisClusterNodes(JedisCluster jedisCluster) {
        // 获取 JedisCluster 中的 connectionHandler 字段
        try {
            Class<?> superclass = jedisCluster.getClass().getSuperclass();
            Field connectionHandlerField = superclass.getDeclaredField("connectionHandler");
            connectionHandlerField.setAccessible(true);
            JedisClusterConnectionHandler connectionHandler = (JedisClusterConnectionHandler) connectionHandlerField.get(jedisCluster);

            // 获取 JedisSlotBasedConnectionHandler 中的 cache 字段
            if (connectionHandler instanceof JedisSlotBasedConnectionHandler) {
                Class<?> superclass1 = connectionHandler.getClass().getSuperclass();
                Field cacheField = superclass1.getDeclaredField("cache");
                cacheField.setAccessible(true);
                JedisClusterInfoCache JedisClusterInfo = (JedisClusterInfoCache) cacheField.get(connectionHandler);
                Map<String, JedisPool> nodes = JedisClusterInfo.getNodes();
                return nodes;
            }
        } catch (Exception e) {
            log.error("redis cluster get error", e);
        }
        return null;
    }


}
